/*******************************************************************************
 * Copyright (c) 2008, 2015 Wind River Systems and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 * 
 * Contributors:
 *     Wind River Systems - initial API and implementation
 *******************************************************************************/
package org.eclipse.cdt.examples.dsf.pda.service;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.eclipse.cdt.debug.core.model.ICLineBreakpoint;
import org.eclipse.cdt.debug.core.model.ICWatchpoint;
import org.eclipse.cdt.dsf.concurrent.DataRequestMonitor;
import org.eclipse.cdt.dsf.debug.service.BreakpointsMediator;
import org.eclipse.cdt.dsf.debug.service.BreakpointsMediator2;
import org.eclipse.cdt.dsf.debug.service.BreakpointsMediator2.BreakpointEventType;
import org.eclipse.cdt.dsf.debug.service.BreakpointsMediator2.ITargetBreakpointInfo;
import org.eclipse.cdt.dsf.debug.service.IBreakpointAttributeTranslator2;
import org.eclipse.cdt.dsf.debug.service.IBreakpoints.IBreakpointsTargetDMContext;
import org.eclipse.cdt.examples.dsf.pda.PDAPlugin;
import org.eclipse.cdt.examples.dsf.pda.breakpoints.PDALineBreakpoint;
import org.eclipse.cdt.examples.dsf.pda.breakpoints.PDAWatchpoint;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.debug.core.DebugException;
import org.eclipse.debug.core.model.IBreakpoint;

/**
 * Translator between {@link PDALineBreakpoint} object attributes and 
 * attributes used by the {@link PDABreakpoints} service.  
 * <p>
 * The attribute translator is used by the standard {@link BreakpointsMediator} 
 * service to map between platform breakpoint attributes and target-side DSF
 * breakpoint attributes.  Thus, this object encapsulates the model-specific
 * functionality of synchronizing target side and IDE-side breakpoint objects.  
 * </p>
 */
public class PDABreakpointAttributeTranslator implements IBreakpointAttributeTranslator2 {

    // Arrays of common attributes between the two breakpoint types.  These 
    // attributes can be copied directly without translation.
    private static final String[] fgPDALineBreakpointAttributes = {
        IBreakpoint.ENABLED,
        IMarker.LINE_NUMBER,
    };
    private static final String[] fgPDAWatchpointAttributes = {
        IBreakpoint.ENABLED,
        PDAWatchpoint.FUNCTION_NAME,
        PDAWatchpoint.VAR_NAME,
        PDAWatchpoint.ACCESS,
        PDAWatchpoint.MODIFICATION
    };

    private static final String[] fgCDTWatchpointAttributes = {
        IBreakpoint.ENABLED,
        PDAWatchpoint.FUNCTION_NAME,
    };

    // PDA breakpoints translator doesn't keep any state and it doesn't 
    // need to initialize or clean up.
    @Override
    public void initialize(BreakpointsMediator2 mediator) {
    }


    @Override
    public void dispose() {
    }

    private List<Map<String, Object>> getBreakpointAttributes(IBreakpoint bp, boolean bpManagerEnabled) 
        throws CoreException 
    {
        if (bp instanceof ICLineBreakpoint) { 
            return getCBreakpointAttributes((ICLineBreakpoint)bp, bpManagerEnabled);
        } else if (bp instanceof ICWatchpoint) {
            return getCWatchpointAttributes((ICWatchpoint)bp, bpManagerEnabled);
        } else {
            return getPDABreakpointAttributes(bp, bpManagerEnabled);
        }
    }

    private List<Map<String, Object>> getCBreakpointAttributes(ICLineBreakpoint bp, boolean bpManagerEnabled) 
        throws CoreException 
    {
        Map<String, Object> attrs = new HashMap<String, Object>(); 

        // Check that the marker exists and retrieve its attributes.  
        // Due to accepted race conditions, the breakpiont marker may become null 
        // while this method is being invoked.  In this case throw an exception
        // and let the caller handle it.
        IMarker marker = bp.getMarker();
        if (marker == null || !marker.exists()) {
            throw new DebugException(new Status(IStatus.ERROR, PDAPlugin.PLUGIN_ID, DebugException.REQUEST_FAILED, "Breakpoint marker does not exist", null)); 
        }
        // Suppress cast warning: platform is still on Java 1.3
        Map<String, Object> platformBpAttrs = marker.getAttributes();

        // Copy breakpoint attributes.
        attrs.put(PDABreakpoints.ATTR_BREAKPOINT_TYPE, PDABreakpoints.PDA_LINE_BREAKPOINT);
        attrs.put(PDABreakpoints.ATTR_PROGRAM_PATH, bp.getFileName());

        copyAttributes(platformBpAttrs, attrs, fgPDALineBreakpointAttributes);

        // If the breakpoint manager is disabled, override the enabled attribute.
        if (!bpManagerEnabled) {
            attrs.put(IBreakpoint.ENABLED, false);
        }

        // The breakpoint mediator allows for multiple target-side breakpoints 
        // to be created for each IDE breakpoint.  Although in case of PDA this 
        // feature is never used, we still have to return a list of attributes.
        List<Map<String, Object>> retVal = new ArrayList<Map<String, Object>>(1);
        retVal.add(attrs);
        return retVal;        
    }

    private List<Map<String, Object>> getCWatchpointAttributes(ICWatchpoint bp, boolean bpManagerEnabled) 
        throws CoreException 
    {
        Map<String, Object> attrs = new HashMap<String, Object>(); 

        // Check that the marker exists and retrieve its attributes.  
        // Due to accepted race conditions, the breakpiont marker may become null 
        // while this method is being invoked.  In this case throw an exception
        // and let the caller handle it.
        IMarker marker = bp.getMarker();
        if (marker == null || !marker.exists()) {
            throw new DebugException(new Status(IStatus.ERROR, PDAPlugin.PLUGIN_ID, DebugException.REQUEST_FAILED, "Breakpoint marker does not exist", null)); 
        }
        // Suppress cast warning: platform is still on Java 1.3
        Map<String, Object> platformBpAttrs = marker.getAttributes();

        // Copy breakpoint attributes.
        attrs.put(PDABreakpoints.ATTR_BREAKPOINT_TYPE, PDABreakpoints.PDA_WATCHPOINT);
        attrs.put(PDABreakpoints.ATTR_BREAKPOINT_TYPE, PDABreakpoints.PDA_WATCHPOINT);
        attrs.put(PDAWatchpoint.VAR_NAME, platformBpAttrs.get(ICWatchpoint.EXPRESSION));
        attrs.put(PDAWatchpoint.ACCESS, platformBpAttrs.get(ICWatchpoint.READ));
        attrs.put(PDAWatchpoint.MODIFICATION, platformBpAttrs.get(ICWatchpoint.WRITE));

        copyAttributes(platformBpAttrs, attrs, fgCDTWatchpointAttributes);

        // If the breakpoint manager is disabled, override the enabled attribute.
        if (!bpManagerEnabled) {
            attrs.put(IBreakpoint.ENABLED, false);
        }

        // The breakpoint mediator allows for multiple target-side breakpoints 
        // to be created for each IDE breakpoint.  Although in case of PDA this 
        // feature is never used, we still have to return a list of attributes.
        List<Map<String, Object>> retVal = new ArrayList<Map<String, Object>>(1);
        retVal.add(attrs);
        return retVal;        
    }

    private List<Map<String, Object>> getPDABreakpointAttributes(IBreakpoint bp, boolean bpManagerEnabled) 
        throws CoreException 
    {
        Map<String, Object> attrs = new HashMap<String, Object>(); 

        // Check that the marker exists and retrieve its attributes.  
        // Due to accepted race conditions, the breakpiont marker may become null 
        // while this method is being invoked.  In this case throw an exception
        // and let the caller handle it.
        IMarker marker = bp.getMarker();
        if (marker == null || !marker.exists()) {
            throw new DebugException(new Status(IStatus.ERROR, PDAPlugin.PLUGIN_ID, DebugException.REQUEST_FAILED, "Breakpoint marker does not exist", null)); 
        }
        // Suppress cast warning: platform is still on Java 1.3
        Map<String, Object> platformBpAttrs = marker.getAttributes();

        // Copy breakpoint attributes.
        if (bp instanceof PDAWatchpoint) {
            attrs.put(PDABreakpoints.ATTR_BREAKPOINT_TYPE, PDABreakpoints.PDA_WATCHPOINT);

            copyAttributes(platformBpAttrs, attrs, fgPDAWatchpointAttributes);
        } else if (bp instanceof PDALineBreakpoint) {
            attrs.put(PDABreakpoints.ATTR_BREAKPOINT_TYPE, PDABreakpoints.PDA_LINE_BREAKPOINT);
            attrs.put(PDABreakpoints.ATTR_PROGRAM_PATH, marker.getResource().getFullPath().toString()); 

            copyAttributes(platformBpAttrs, attrs, fgPDALineBreakpointAttributes);
        }

        // If the breakpoint manager is disabled, override the enabled attribute.
        if (!bpManagerEnabled) {
            attrs.put(IBreakpoint.ENABLED, false);
        }

        // The breakpoint mediator allows for multiple target-side breakpoints 
        // to be created for each IDE breakpoint.  Although in case of PDA this 
        // feature is never used, we still have to return a list of attributes.
        List<Map<String, Object>> retVal = new ArrayList<Map<String, Object>>(1);
        retVal.add(attrs);
        return retVal;
    }

    private void copyAttributes(Map<String, Object> srcMap, Map<String, Object> destMap, String[] attrs) {
        for (String attr : attrs) {
            if (srcMap.containsKey(attr)) {
                destMap.put(attr, srcMap.get(attr));
            }
        }
    }

    @Override
    public boolean canUpdateAttributes(IBreakpoint bp, IBreakpointsTargetDMContext context, 
        Map<String, Object> attributes) 
    {
        // PDA debugger only allows updating of the action property of the watchpoint.
        // All other breakpoint updates will require a re-installation.
        if (bp instanceof PDAWatchpoint) {
            Map<String, Object> deltaCopy = new HashMap<String, Object>(attributes);
            deltaCopy.remove(PDAWatchpoint.ACCESS);
            deltaCopy.remove(PDAWatchpoint.MODIFICATION);
            return !deltaCopy.isEmpty();
        }
        return false;
    }

    @Override
    public boolean supportsBreakpoint(IBreakpoint bp) {
        return bp.getModelIdentifier().equals(PDAPlugin.ID_PDA_DEBUG_MODEL) ||
            bp instanceof ICLineBreakpoint || bp instanceof ICWatchpoint;
    }

    @Override
    public void updateBreakpointsStatus(
        Map<IBreakpoint, Map<IBreakpointsTargetDMContext, ITargetBreakpointInfo[]>> bpsInfo,
        BreakpointEventType eventType) 
    {
        for (IBreakpoint bp : bpsInfo.keySet()) {
            if (!(bp instanceof ICLineBreakpoint)) {
                continue;
            }
            ICLineBreakpoint cbp = (ICLineBreakpoint)bp;
            try {
                if (eventType == BreakpointEventType.ADDED) {
                    cbp.incrementInstallCount();
// Testing for Bug 360280 - [breakpoints] Reposition breakpoints when planted on invalid line                    
//                    if (cbp instanceof ICLineBreakpoint2) {
//                        ICLineBreakpoint2 lbp2 = (ICLineBreakpoint2)cbp;
//                        lbp2.setInstalledLineNumber(lbp2.getRequestedLine() + 1);
//                    }
                } else if (eventType == BreakpointEventType.REMOVED) {
                    cbp.decrementInstallCount();
                }
            } catch (CoreException e) {}
        }
    }

    @Override
    public void resolveBreakpoint(IBreakpointsTargetDMContext context, IBreakpoint breakpoint,
        Map<String, Object> bpAttributes, DataRequestMonitor<List<Map<String, Object>>> drm) 
    {
        try {
            drm.setData( getBreakpointAttributes(breakpoint, true) );
        } catch (CoreException e) {
            drm.setStatus(e.getStatus());
        }
        drm.done();
    }

    @Override
    public Map<String, Object> getAllBreakpointAttributes(IBreakpoint platformBP, boolean bpManagerEnabled)
        throws CoreException 
    {
        IMarker marker = platformBP.getMarker(); 
        if (marker == null) {
            throw new DebugException(new Status(IStatus.ERROR, PDAPlugin.PLUGIN_ID, "Null marker for breakpoint: " + platformBP));
        }
        return marker.getAttributes();
    }

    @Override
    public Map<String, Object> convertAttributes(Map<String, Object> platformBPAttr) {
        return platformBPAttr;
    }

    
}
